; Grange Murder special game logic. Graham M Jones 03/06/89. 
;
 begin
cif debugmode
code -
 prs "*****AALOGIC*****" ; debug text
code +
cend
;
; Actor has just left FROM in direction DIR to enter ROOM
.AFTERMOVES
;
;=====
; set momentary flag when Actor goes via front door, so as to let in 
; anybody waiting on arrival
 if room<>86 then notenterhouse
 if from=47 then openfrontdoor
.notenterhouse
 if room<>47 then notleavehouse
 if from<>86 then notleavehouse
.openfrontdoor
 frontdooropened=true
.notleavehouse
;=====
;
.amret
 RETURN
;---
.SPECIALMOVES
; Given ROOM, HIDEST, DEST, ACTOR is to move that way
; and exit has been validated as possible (and hence described)
; in SPECIALEXITS. The purpose of code here is when something
; happens on moving. If exit is to be blocked, code here should
; print the appropriate message and set RESULT=FALSE.
;
 if hidest<>0 then @smok
;
;=====
; make sure npc's don't arrive in game until jarvis has finished 
; escorting the last npc to his or her room.
 if actor=kathy then arriveingame
 if actor=anthony then arriveingame
 if actor=clifford then arriveingame
 if actor<>sue then notarriveingame
.arriveingame
 if room<>3 then notarriveingame
 if dest<>45 then notarriveingame
 if personarriving<>false then smprevent
 personarriving=actor
.notarriveingame
;---
; make sure anyone involved doesn't meet user while the murder 
; takes place...
 if Vreadyformurder<>true then notinvolvednpc
 if actor=involvednpc1 then testifuserhere
 if actor=involvednpc2 then testifuserhere
 if actor<>involvednpc3 then notinvolvednpc
.testifuserhere
 if dest<>currentuserroom then notinvolvednpc
 gosub @distractuser
 goto smprevent
.notinvolvednpc
;=====
;
.smok
 RESULT=TRUE
 RETURN
;
.smprevent
 result=FALSE
 return
;---
.SPECIALEXAMINE
; print any special examine messages which follow
; the basic message but are before any contents are printed
; set processed=TRUE if the contents should not be printed
 return
;---
.SPECIALPREEXAMINE
; before printing examine message / contents, check if
; anything should prevent this. Return TRUE if details should
; NOT be printed
;
;=====
 if Murder<>1 then SPENotM1
; Examines for murder 1...
;---
 if vmurderbeendone=false then SPENotAfterM1
; Examines after murder 1 has taken place...
;---
 m1=30200 ; apron is bloodstained after the murder
 if object=pinnafore then @SPETruem1
;---
 m1=30201 ; knife is bloodstained after the murder
 if object=breadknife then @SPETruem1
;---
 m1=30202 ; sue has been stabbed
 if object=sue then @SPETruem1
;---
.SPENotAfterM1
;---
.SPENotM1
;=====
;
 RESULT=FALSE ; proceed normally
 RETURN
;
.SPETruem1
 gosub @printm1dot
 result=true ; don't print examine message
 return
;---
; CHECK IF ACCESSIBLE BEFORE CHECKING OBVIOUS THINGS LIKE OBJECT IS
; OMNI PRESENT, OR OWNED BY SOMEONE ELSE.
;
.specialcheckifaccessible
; if no special code is needed, LEAVE RESULT UNCHANGED
; otherwise return RESULT=TRUE if OBJECT is accessible to VERB
; or RESULT=FALSE if not accessible.
; *graham* this is necessary when using two objects for a wardrobe etc.
; (i.e. a wardrobe which is open, and one which is closed)
 if processingsay=TRUE then conditionaltrue ;*graham* bug fix. sciatrue
.sciaNotAutomatic
 if verb=ifollow then conditionaltrue
 if verb=isetupgo then conditionaltrue
 if verb=igdgo then conditionaltrue
 if verb=isetupfind then conditionaltrue
 if verb=igdfind then conditionaltrue
 if verb=igetme then sciatrue
 if verb=iwaitforperson then conditionaltrue ; wait for ...
 if verb=iwaitforperiod then conditionaltrue ; wait for ...
 if verb=iwaituntiltime then conditionaltrue ; wait until ...
 return
;---
.conditionaltrue
; only true if OBJECT exists in the game
 x1=currentpos(object)
 if x1=0 then @returnfalse
.sciatrue
 result=TRUE
 return
;---
.SpecialConjugate
; return x1 as type for noun x2 (if different to that in the table)
 if x2=vera then scProperFemale
 if x2=sue then scProperFemale
 if x2=fiona then scProperFemale
 if x2=kathy then scProperFemale
.scNotPF
 return
.scProperFemale
 x1=ProperFemale
 return
;---
; ACTOR is about to be activated. Make any priority decisions here.
; Set processed=true if next stack command should NOT be executed.
; Set Verb<>0 to execute that command instead of the next one stacked.
.specialactivatenpc
;
;=====
; make user isn't in same room as someone involved with the murder 
 if actor<>user then sandontdistractuser
 x1=currentuserroom
 gosub @distractuserinroomx1
.sandontdistractuser
;=====
;
 return
;---
; clear keyboard buffer
.ClearKBD
; get keys until we have a zero one, then return
; the last non-zero
 gosub @MCOsRdCh
 if v1<>0 then ClearKBD
 return
;---
; ACTOR is just about to print M1 as part of a racetrack
; instruction. Use this hook to intercept particular things
; which may happen in the message which cannot be easily
; coded elsewhere.
;
; Set RESULT=FALSE if message should NOT be printed
;
.specialrtmessage
;
cif debugmode
; message 4999 is a general-purpose debugging marker
 if m1<>4999 then srtNotDebug
code -
 message cr
 prs "RT DEBUG "
 print actor
 message cr
code +
.srtNotDebug
cend
;
;=====
; messages 2700-2799 are used to re-enable manual control when 
; the player has finished a distraction racetrack
 if m1<2700 then notcanceluserrt
 if m1>2799 then notcanceluserrt
 gosub @ClearKBD ; clear keyboard buffer
; put phone down for most rt's
 if executingracetrack<20 then srtReachPhone
 if executingracetrack=27 then srtReachPhone
 if executingracetrack<>28 then srtNotReachPhone
.srtReachPhone
push m1
 sFrames=3
 ObjectNumber=DropPhone
 x1=1 ; facing north
 gosub @AAAnimateGotDir
pop m1
.srtNotReachPhone
 executingracetrack=false
 goto @srtOk
.notcanceluserrt
;---
; messages 5250-8 are used when jarvis has finished showing somebody 
; to their room, and is free to answer the door for somebody else
 if m1<5250 then jarvisnotshowroom
 if m1>5258 then jarvisnotshowroom
 personarriving=false
 goto @srtOK
.jarvisnotshowroom
;---
; message 5259 is used when all the guests have arrived, and Jarvis is 
; to begin his proper racetrack
 if m1<>5259 then notjarvisstartrt
 object=jarvis
 x6=jarvis
;
; add specific block of 12 racetracks per the murder (1-9)
 x2=murder
 x3=12
.rtaddmurderoffset
 sub x2,c1
 if x2<1 then gotjarvisrt
 add x6,x3
 goto rtaddmurderoffset
;
.gotjarvisrt
 gosub @newracetrackforobject
 goto @srtOK
;
.notjarvisstartrt
;---
; Messages 8000-8009 are used to trigger the guests to go to dinner
 if m1<8000 then notdinnerready
 if m1>8009 then notdinnerready
 Vdinnerisready=true
 goto @srtok
.notdinnerready
;---
; Messages 8010-8019 are used to trigger the murder
 if m1<8010 then notmurderready
 if m1>8019 then notmurderready
 Vreadyformurder=true
 gosub @setmurderflags ; set some pre-murder flags
;
; distract player if he's in same room as anyone involved in 
; the murder...
 x1=currentuserroom
 gosub @distractuserinroomx1
 goto @srtOK
;
.notmurderready
;---
; Message 8020 is used when all guests have arrived for dinner, and dinner 
; is to be served
 if m1<>8020 then notdinnerserved
 Vdinnerisserved=true
 goto @srtOK
.notdinnerserved
;---
; Messages 8030-39 signify that the murder has just taken place, 
; so begin new 'post-murder' racetracks for all the guests...
 if m1<8030 then @notmurderdone
 if m1>8039 then @notmurderdone
code -
 message cr
 message 8030 ; message is omni-visible
code +
;
; set post murder racetrack commands for npcs
push actor
 actor=1
.PMinitracetrack1
; now find pointer to initial racetrack for actor:
 add actor,c1
 gosub @stop ; cancel current racetrack
 x1=actor ; number of racetrack to activate
; add specific block of 12 racetracks per the murder (1-9)
 x2=murder
 x3=12
.PMaddmurderoffset
 sub x2,c1
 if x2<1 then PMgotmurderoffset
 add x1,x3
 goto PMaddmurderoffset
.PMgotmurderoffset
 x2=130 ; offset for Post Murder racetracks
 add x1,x2
 gosub @initracetrackx1
 if actor<maxnpc then PMinitracetrack1
pop actor
 gosub @setactorattributes
 Vmurderbeendone=true
; plant the breadknife on vera!
 object=breadknife
 pos=vera
 hipos=carried
 gosub @MoveObject
 gosub @clearQStack ; clear stack of repeated questions
 goto @srtOK
;
.notmurderdone
;---
; Message 8040 is used after the murder to make actor obedient to 
; commands given by the player
 if m1<>8040 then notmakeobedient
 gosub @setactorattributes
 x1=ObedienceOffset
 add x1,ActorAttributes
 npccurrent(x1)=c1 ; test this in conversation stuff
 goto @srtOK
.notmakeobedient
;---
; Message 8050 is used to set a flag when jarvis phones the police
 if m1<>8050 then jarvisnotphone
 Vjarvisphonedpolice=true
 goto @srtOK
.jarvisnotphone
;---
; Messages 8070-9 are used when the guilty party has finished the 
; murder sequence, and it's okay to meet them again without being 
; distracted
 if m1<8070 then notfinishedmurder
 if m1>8079 then notfinishedmurder
 Vreadyformurder=false
 goto @srtOK
.notfinishedmurder
;=====
;
; Go ahead with the message...
.srtOK
;
;=====
; Don't print any other 'talking' messages while player is 
; executing a distraction racetrack, since this can become 
; very confusing...
 if actor=user then srtok1
 if executingracetrack=false then srtok1
; allow for special 'important' messages related to the 
; murder
 if m1<8000 then @srtPrevent
 if m1>10000 then @srtPrevent
.srtok1
;=====
;
 result=true
 return
;
.srtPrevent
; Don't print the message...
 result=false
 return
;
;=====
; Set any special flags for this murder...
.SetMurderFlags
 involvednpc1=0 ; always set top the victim
 involvednpc2=0
 involvednpc3=0
;
 if murder=2 then setupnpcsinvolved2
 if murder=3 then setupnpcsinvolved3
 if murder=4 then setupnpcsinvolved4
 if murder=5 then setupnpcsinvolved5
 if murder=6 then setupnpcsinvolved6
 if murder=7 then setupnpcsinvolved7
 if murder=8 then setupnpcsinvolved8
 if murder=9 then setupnpcsinvolved9
;
; setupnpcsinvolved1
 involvednpc1=sue ; always set to the victim
 involvednpc2=vera
 return
;
.setupnpcsinvolved2
 return
;
.setupnpcsinvolved3
 return
;
.setupnpcsinvolved4
 return
;
.setupnpcsinvolved5
 return
;
.setupnpcsinvolved6
 return
;
.setupnpcsinvolved7
 return
;
.setupnpcsinvolved8
 return
;
.setupnpcsinvolved9
 return
;---
; user is in, or about to enter room x1. distract him if neccessary...
.distractuserinroomx1
 if Vreadyformurder=false then irxifalse
 gosub @isroomx1involved
 if result=true then distractuser
 return
;
; RESULT=TRUE if user should be distracted if he's in room x1 while 
; the murder is taking place?
.isroomx1involved
 result=true
; It's okay to visit the victim just after the murder, provided 
; we don't meet the guilty party while they clear things up 
; etc...
 if Vmurderbeendone=true then oktomeetvictim
 x2=currentpos(involvednpc1)
 if x2=x1 then irxiret
.oktomeetvictim
 x2=currentpos(involvednpc2)
 if x2=x1 then irxiret
 x2=currentpos(involvednpc3)
 if x2=x1 then irxiret
.irxifalse
 result=false
.irxiret
 return
;
; Jarvis 'arrives' to distract user from the scene of the murder...
.distractuser
 if executingracetrack<>false then irxiret ; already distracted!!
push actorattributes
push m1
push acbheader
push room
push actor
push object
push dir
;
 acbheader=640 ; jarvis(10)*ACBSize(64)
 gosub @ReadHiresCoords
push ObjectNumber
;
x1=currentpos(jarvis)
push x1
;
 object=jarvis
 actor=jarvis
 room=currentuserroom
 simulatejarvis=false
 if x1=room then @getdistractionnum ; jarvis already here
 currentpos(jarvis)=currentuserroom
 simulatejarvis=true
;
 code -
 message 2822
 code +
;
; jarvis enters room
 x1=startfloorpointers
 add x1,currentuserroom
 x1=list5(x1) ; get direction to leave this room
 add x1,startreversaltable
 dir=list5(x1) ; direction in which jarvis will enter
 gosub @ABSObjectArrives
;
 sFrames=0
.DU1
 KeyPadDirection=107 ; stop player moving (num key '5')
 gosub @BuildAndDisplayFrame
 if sFrames<8 then DU1 ; allow jarvis to enter a few steps
;
; get a random distraction
.getdistractionnum
 x2=30
 gosub @randomx1modx2 ; x1=random 0-29
 executingracetrack=x1 ; disable manual control
 add executingracetrack,c1 ; 1-30
 add x1,x1
 add x1,x1 ; table in entries of 4 bytes
 x2=startdistractions
 add x2,x1 ; x2 is pointer into data table
;
; get data for this distraction
 &m1=list5(x2) ; get initial message
 add x2,c2
 x1=list5(x2)
 if x1=currentuserroom then @getdistractionnum ; we're in this room!
push x2
 gosub @isroomx1involved
pop x2
 if result=true then @getdistractionnum ; can't use this room!
 wantnewroom=x1 ; set player to enter this room
 add x2,c1
 UserDirection=list5(x2) ; direction in which to enter
 currentpos(user)=wantnewroom
;
code -
 message cr
 message 310 ; Jarvis
 message m1 ; said, 'phone for you' etc...
 message dot
code +
;
; send the player on a corresponding racetrack
 actor=user
 gosub @stop ; cancel any pending commands
 x1=239 ; base-1 for distraction racetracks
 add x1,executingracetrack
 gosub @initracetrackx1
;
; jarvis stands still for a while
 if simulatejarvis<>true then @dontsimulatejarvis
 acbheader=640 ; jarvis(10)*ACBSize(64)
 gosub @getvaliddirx1
 dv1=standinganimation
 add dv1,x1
 dx4=ACBHeader
 gosub @AlterACB
;
 sFrames=0
.DU2
 KeyPadDirection=107 ; stop player moving (num key '5')
 gosub @BuildAndDisplayFrame
 if sFrames<15 then DU2 ; allow time to read message
;
; reverse jarvis...
 acbheader=640 ; jarvis(10)*ACBSize(64)
 gosub @getvaliddirx1
 add x1,startreversaltable
 x1=list5(x1) ; reverse x1
 dv1=movinganimation
 add dv1,x1
 dx4=ACBHeader
 gosub @AlterACB
;
 sFrames=0
.DU3
 KeyPadDirection=107 ; stop player moving (num key '5')
 gosub @BuildAndDisplayFrame
 if sFrames<8 then DU3 ; allow time to read message
 goto distractiondone
;
; use a time delay to make up for animation if jarvis 
; was already in the room to give the distraction...
.dontsimulatejarvis
 x1=0
.distractdelay
 add x1,c1
 if x1<100000 then distractdelay
;
.distractiondone
pop x1
currentpos(jarvis)=x1
;
 acbheader=640 ; jarvis(10)*ACBSize(64)
pop ObjectNumber
 gosub @WriteHiresCoords ; recover old hires co-ords
;
pop dir
pop object
pop actor
pop room
pop acbheader
pop m1
pop actorattributes
 result=true
 return
;=====
;
.SPECIALACTOR
;
;=====
; special case to allow player to dash straight to a 
; location when distracted from the scene of the murder
 if actor<>user then notuserRt
 if executingracetrack=false then notuserRt
code -
 message 2823
code +
 return
.notuserRt
;=====
;
 lastwordprinted=actor ; force it to print the article - 'you'
 verb=iam
 goto @printACTORverb
;---
; Special conditional traps for high noun1 values (i.e. > 249)
; RESULT=TRUE if condition is met
.SpecialRtConditional
;
;=====
; 'intermittent' conditional. TRUE when <GAME TIMER> MOD Noun2=prep
 if noun1<>intermittent then srtcnotinter
 x2=prep
 prep=0 ; aviod any later parser confusion!
;
; if x2 (prep) > 250 then use a multiple of Actor (actor*(x2-250))
 if x2<251 then intermit2
 x1=0
.intermit1
 add x1,actor
 sub x2,c1
 if x2>250 then intermit1
 x2=x1
;
.intermit2
 x1=noun2
 and x1,NpcLoopTimer
 if x1<>x2 then @ReturnFalse
 goto @returntrue ; condition met
.srtcnotinter
;---
; is dinner ready?
 if noun1<>dinnerisready then srtcnotdinnerready
 result=VDinnerIsReady
 return
.srtcnotdinnerready
;---
; are all guests here (used in dining room before serving dinner)
 if noun1<>dinnerisserved then srtcnotdinnerserved
 result=VDinnerIsServed
 return
.srtcnotdinnerserved
;---
; has murder been triggered?
 if noun1<>readyformurder then srtcnotmurder
 result=VReadyForMurder
 return
.srtcnotmurder
;---
; has jarvis phoned the police?
 if noun1<>jarvisphonedpolice then srtcnotpolice
 result=Vjarvisphonedpolice
 return
.srtcnotpolice
;---
; return true if room is empty (apart from actor AND user)
 if noun1<>isroomemptyexceptuser then srtcNotEmpty
 object=2 ; start after user
 goto testempty
.srtcNotEmpty
;---
; return true if room is empty (apart from actor)
 if noun1<>isroomempty then srtcret
 object=1 ; start at user
.testempty
 if object=actor then emptysofar ; skip actor
 gosub @checkifpresent
 if result=true then srtc1 ; somebody here!
.emptysofar
 add object,c1
 if object<maxnpcplus1 then testempty
; result=false...
;
.srtc1
 xor result,c1 ; reverse result
 return
;=====
;
.srtcret
 return
;
;---
.ReadHiresCoords
 x1=ACBHeader
 &ObjectNumber=Hires(x1)
 add x1,c2
 &ActorX=Hires(x1)
 add x1,c2
 &ActorZ=Hires(x1)
 add x1,c2
 &ActorH=Hires(x1)
 return
;---
.WriteHiresCoords
 x1=ACBHeader
 &Hires(x1)=ObjectNumber
 add x1,c2
 &Hires(x1)=ActorX
 add x1,c2
 &Hires(x1)=ActorZ
 add x1,c2
 &Hires(x1)=ActorH
 return
;---
.GDreadSquareVec
 goto @GDReadSquare
;---
.GDpointOnMapVec
 gosub @GDpointOnMap
;---
.GDQuadMaskVec
 goto @GDQuadMask
;---
.DisplayBackdropVec
 goto @DisplayBackdrop
;---
.DisplayFrameVec
 goto @DisplayFrame
;---
.WaitForFrameVec
 goto @WaitForFrame
;---
.BuildAndDisplayFrameVec
 goto @BuildAndDisplayFrame
;---
.ShowNewPersonVec
 goto @ShowNewPerson
;---
.CalcFastestRouteVec
 goto @calcFastestRoute
;---
.CheckForExitCollisionVec
 goto @CheckForExitCollision
;---
.RealToPhysicalDirVec
 goto @RealToPhysicalDir
;---
